#ifndef GENERALIZATION_H
#define GENERALIZATION_H

#include "QuerySpecification.h"
#include "shared_object.h"
#include <thrust/device_vector.h>
#include <thrust/transform_reduce.h>
#include <thrust/functional.h>
#include <stdio.h>
#include <vector>
#include <string>
using namespace std;
using namespace thrust;

/* GPU constants */
#define MAX_DIM 128			// max dimension size of keyword
#define MAX_DIM_VALUE 256	// max dimension value of keyword
#define THREAD_PER_BLK 128  // must be greater or equal to MAX_DIM and not exceed 1024

/* invert list specifications on GPU, won't change throughout the lifet time of the program*/
struct InvertListSpecGPU{
	int totalDimension;
	int numOfDocToExpand;
	int numOfQuery;
	int minDomainForAllDimension[MAX_DIM];	// inclusive bound
	int maxDomainForAllDimension[MAX_DIM];	// inclusive bound
	int maxFeatureID;						// exclusive ID, all id must be smaller than this number
};

/* the entry of a inverted list, this number must not exceed QuerySpecGPU.maxFeatureID */
typedef int InvlistEnt; 

/* keep track of the last positions the counting computed */
struct QueryInfo{
	float keyword[MAX_DIM];		// keyword for this query
	int2 lastPos[MAX_DIM];		// lastPos[i].x means the down moved step, lastPos[i].y means the up moved step, all elements are valid
								// before total dimension in case the query change the search dimension in mid of search
	int numOfDimensionToSearch; // number of dimension to search

	// all the array below, the entries are only valid up to index smaller than numOfDimensionToSearch
	int searchDim[MAX_DIM];		// index represent the dimension to search
	float dimWeight[MAX_DIM];	// weight of this perticular dimension
	int distanceFunc[MAX_DIM];	// distance function for specific dimension ???
	float upperBoundDist[MAX_DIM];		// upper bounding function to compute distance function
	float lowerBoundDist[MAX_DIM];		// lower bounding function to compute distance function

	int upperBoundSearch[MAX_DIM];	// search bound when going up
	int lowerBoundSearch[MAX_DIM];	// search boudn when going down

	int aggregateFunc;			// aggregation function ???

	__host__ __device__ QueryInfo(){
		for(int i = 0; i < MAX_DIM; i++){
			keyword[i] = -1;	// init with invalid value
			lastPos[i].x = 0;	// init with invalid value
			lastPos[i].y = 0;	// init with invalid value
			dimWeight[i] = 1;	// init with equal weight
			searchDim[i] = -1;	// init with invalid value
			distanceFunc[i] = 0;// init with default distance function
			upperBoundDist[i] = 0;
			lowerBoundDist[i] = 0;

			// init with maximal possible value, they are all reletive position according to query keyword
			upperBoundSearch[i] = MAX_DIM_VALUE; 
			lowerBoundSearch[i] = MAX_DIM_VALUE;
		}
		
	}
};

/* the entry of mapping from query to feature */
struct QueryFeatureEnt{
	int count; // under current setup, count is actually integer and max is MAX_DIM
	float ACD; // accumulated distance

	__host__ __device__ QueryFeatureEnt(){
		count = 0;
		ACD = 0;
	}
};

/* GPU device constants */
__constant__ InvertListSpecGPU invert_list_spec;	// query spec on GPU device, fixed throughout the lifetime of the program

class QueryManager{
public:
	/* 
	if query_sepc has empty file destination, then use random invert list with list size as rand_invert_list_size 
	however in query_spec totalDimension,numOfDocToExpand,minDomainForAllDimension,maxDomainForAllDimension and topK must be specified and valid
	if invertedListPath is specified, then it should be the directory containing all invert list, format should be ../../dir with no / at the end
	*/
	QueryManager(QuerySpecification& query_spec,int rand_invert_list_size);

	/* clear all gpu memory */
	~QueryManager();

	/* setup a new batch of query */
	void init_new_query(vector<GpuQuery>& query_set,int rand_query_size);

	/* 
	run this batch of query which just initialized, should not be mixed with point_query
	threshold is for count, if count > threshold, then result will be considered
	*/
	bool bi_direction_query(vector<Result>& result,int threshold);

	/* 
	this method should be only call once, then no more calls including bi_direciton query should be made 
	threshold is for count, if count > threshold, then result will be considered
	*/
	void point_query(vector<Result>& result, int threshold);


	/* ---------------------------------- debug --------------------------------------- */
	void print_query();
	void print_query_doucment_mapping();
	void print_invert_list();

private:
	/* ------------------------------init functions ---------------------------------*/
	// random generate inverted list
	void rand_inv_list();
	// random generate query
	void rand_query(int query_num);

	// retrieve invert list given the directory
	void get_invert_list_from_file(string invert_list_dir,string invert_list_idx_file);

	// retrieve invert list from binary given the filename
	void get_invert_list_from_binary_file(string filename);

	// clear memory to setup new round of query search
	void clear_query_memory();

	/* -------------------------------- fields --------------------------------------*/
	// query sepc on CPU side
	InvertListSpecGPU invert_list_spec_host;

	// top K results will be selected
	int topK;
	
	// inverted list and the index
	device_vector<InvlistEnt>* d_invert_list; 
	device_vector<int>* d_invert_list_idx;

	// query to keyword: each query associate with (query_spec_host.totalDimension) keywords
	device_vector<QueryInfo>* d_query_info;
	
	// result of query to feature result
	device_vector<QueryFeatureEnt>* d_query_feature;
	
	// record which query is still running, a entry is 1 if it is still running, 0 means the query has stopped
	device_vector<int>* d_valid_query;

	// number of rounds that the query has been processed
	int num_of_rounds;

	// indict whether current batch of query is initialized or not 
	bool query_initialized;

	
	
};


#endif